﻿using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Collections.Generic;

namespace Sonce.Editor
{
	/// <summary>
	/// Manage transformations, order and alignment of visual objects.
	/// </summary>
	public partial class LayoutManager
	{
		#region Constructor
		/// <summary>
		/// Initializes a new LayoutManager instance.
		/// </summary>
		public LayoutManager()
		{
		}
		#endregion

		public void SetAbsolutePosition(double x, double y)
		{
			SetAbsolutePosition(new Point(x, y));
		}

		public void SetAbsolutePosition(Point point)
		{
#warning Method not implemented
		}

		#region Transitions
		/// <summary>
		/// Move an element to the new location in it's container.
		/// </summary>
		/// <param name="element">FrameworkElement to be moved.</param>
		/// <param name="point">Absolute or relative position.</param>
		/// <param name="isRelativeMove">True to move relatively, false to move to absolute position (according to the parent control).</param>
		/// <returns>Returns true on success.</returns>
		public bool Move(FrameworkElement element, Point point, bool isRelativeMove)
		{
			bool completed = false;
			if (element != null)
			{
				if (isRelativeMove)
				{
					Point absolutePoint = GetAbsolutePosition(element);
					SetAbsolutePosition(absolutePoint.X + point.X, absolutePoint.Y + point.Y);
					completed = true;
				}
				else
				{
					element.Margin = new Thickness(element.Margin.Left + point.X, element.Margin.Top + point.Y, 0, 0);
					completed = true;
				}
			}
			return completed;
		}

		/// <summary>
		/// Flip element by horizontally or vertically.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		/// <param name="horizontal">True to flip it by the X axis.</param>
		/// <param name="vertical">True to flip it by the Y axis.</param>
		public bool Flip(FrameworkElement element, bool horizontal, bool vertical)
		{
			ScaleTransform scaleTransform = null;
			if (element.RenderTransform != null)
			{
				if (element.RenderTransform is TransformGroup)
				{
					TransformGroup group = element.RenderTransform as TransformGroup;
					foreach (Transform transform in group.Children)
					{
						if (transform is ScaleTransform)
						{
							scaleTransform = transform as ScaleTransform;
							break;
						}
					}
				}
				else if (element.RenderTransform is ScaleTransform)
				{
					scaleTransform = element.RenderTransform as ScaleTransform;
				}
			}
			if (scaleTransform == null)
			{
				scaleTransform = new ScaleTransform();
				element.RenderTransform = scaleTransform;
			}

			scaleTransform.ScaleX = (horizontal ? -scaleTransform.ScaleX : scaleTransform.ScaleX); //Flip by X axis
			scaleTransform.ScaleY = (vertical ? -scaleTransform.ScaleY : scaleTransform.ScaleY); //Flip by Y axis
			return true;
		}

		/// <summary>
		/// Scale element. Use this method also if you want to flip while scaling.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		/// <param name="scaleX">Scale ratio for the X axis. Value 1.0 means original size, lower is smaller, higher is larger, negative is flip.</param>
		/// <param name="scaleY">Scale ratio for the Y axis. Value 1.0 means original size, lower is smaller, higher is larger, negative is flip.</param>
		public bool Scale(FrameworkElement element, double scaleX, double scaleY)
		{
			//TODO: Similar to Flip method
			throw new NotImplementedException("Scale() method is not implemented!");
			return false;
		}

		/// <summary>
		/// Rotate element.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		/// <param name="degrees">Rotation in degrees.</param>
		/// <returns>Returns true if rotation has been performed</returns>
		public bool Rotate(FrameworkElement element, double degrees)
		{
			throw new NotImplementedException("Rotate() method is not implemented!");
			return false;
		}

		public bool Rotate(FrameworkElement element, double degrees, Point origin)
		{
			return false;
		}
		#endregion

		#region Ordering
		public int ChangeContainer(FrameworkElement element, Panel newContainer)
		{
			int index = -1;
			if (newContainer != null && element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				parent.Children.Remove(element);
				newContainer.Children.Add(element);
				index = newContainer.Children.Count - 1;
			}
			return index;
		}

		public int ChangeOrder(FrameworkElement element, int newIndex)
		{
			int index = -1;
			if (element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				if (newIndex >= 0 && newIndex < parent.Children.Count - 1)
				{
					parent.Children.Remove(element);
					parent.Children.Insert(newIndex, element);
					index = newIndex;
				}
			}
			return index;
		}

		public int GetOrderIndex(FrameworkElement element)
		{
			int index = -1;
			if (element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				index = parent.Children.IndexOf(element);
			}
			return index;
		}

		/// <summary>
		/// Move element to the top (render above other elements) in the containing layer.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		public int MoveToFront(FrameworkElement element)
		{
			int index = -1;
			if (element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				parent.Children.Remove(element); //Remove from the middle
				parent.Children.Add(element); //Add to the top
				index = parent.Children.Count - 1;
			}
			return index;
		}

		/// <summary>
		/// Move element to the back (render below other elements) in the containing layer.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		public int MoveToBack(FrameworkElement element)
		{
			int index = -1;
			if (element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				parent.Children.Remove(element); //Remove from the middle
				parent.Children.Insert(0, element); //Add to the bottom
				index = 0;
			}
			return index;
		}

		/// <summary>
		/// Move element one step higher in the containing layer.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		public int MoveForward(FrameworkElement element)
		{
			int index = -1;
			if (element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				int i = parent.Children.IndexOf(element);
				if (i + 1 < parent.Children.Count)
				{
					parent.Children.Remove(element);
					parent.Children.Insert(i + 1, element);
					index = i + 1;
				}
			}
			return index;
		}

		/// <summary>
		/// Move element one step lower in the containing layer.
		/// </summary>
		/// <param name="element">FrameworkElement</param>
		public int MoveBackward(FrameworkElement element)
		{
			int index = -1;
			if (element != null && element.Parent != null && element.Parent is Panel)
			{
				Panel parent = element.Parent as Panel;
				int i = parent.Children.IndexOf(element);
				if (i - 1 >= 0)
				{
					parent.Children.Remove(element);
					parent.Children.Insert(i - 1, element);
					index = i - 1;
				}
			}
			return index;
		}
		#endregion

		#region Alignment
		//TODO: Create aligning methods
		public void Align()
		{
#warning Method not implemented
		}

		public void AlignToGrid()
		{
#warning Method not implemented
		}
		#endregion

		/// <summary>
		/// Transform an visual object.
		/// </summary>
		/// <param name="element"></param>
		/// <param name="angle"></param>
		/// <param name="scaleX"></param>
		/// <param name="scaleY"></param>
		public static void Transform(FrameworkElement element, double? angle, double? scaleX, double? scaleY)
		{
			if (element != null)
			{
				TransformGroup group = new TransformGroup();

				if (angle != null)
				{
					RotateTransform rotate = new RotateTransform();
					rotate.Angle = angle.Value;
					group.Children.Add(rotate);
				}

				if (scaleX != null || scaleY != null)
				{
					ScaleTransform scale = new ScaleTransform();
					scale.ScaleX = (scaleX != null ? scaleX.Value : 1);
					scale.ScaleY = (scaleY != null ? scaleY.Value : 1);
					group.Children.Add(scale);
				}

				element.RenderTransform = group;
			}
		}

		public static Point GetAbsolutePosition(FrameworkElement element)
		{
			Point position = new Point(element.Margin.Left, element.Margin.Top);
			if (element is Path && ((Path)element).Data != null)
			{
				Path path = element as Path;
				position = new Point(path.Data.Bounds.Left, path.Data.Bounds.Top);
			}
			GetAbsolutePosition(element, ref position);
			return position;
		}

		/// <summary>
		/// Get the absolute location of the visual object.
		/// </summary>
		/// <param name="element">[in] FrameworkElement.</param>
		/// <param name="position">[ref] Returns absolute position as Point.</param>
		/// <returns>Returns true if absolute position is successfully retrieved.</returns>
		/// <remarks>Top layer is the Layer object, bubble up to the first Layer.</remarks>
		public static bool GetAbsolutePosition(FrameworkElement element, ref Point position)
		{
			bool completed = false;
			if (element == null || element.Parent == null)
			{
				return false;
			}
			else if (element.Parent is Layer)
			{
				//Stop the recursion
				completed = true;
			}
			else if (element.Parent is Panel)
			{
				Panel panel = element.Parent as Panel;
				position.X = position.X + panel.Margin.Left;
				position.Y = position.Y + panel.Margin.Top;

				//Search recursively
				completed = GetAbsolutePosition(panel, ref position);
			}
			//TODO: Border, UserControl any other container
			return completed;
		}

		//Under construction
		public static bool GetAbsoluteBounds(FrameworkElement element, out Rect bounds)
		{
			//element.RenderSize;
			//http://silverlight.net/forums/t/22763.aspx
			//http://pagebrooks.com/archive/2008/12/07/building-a-resizable-silverlight-control.aspx
			//http://msdn.microsoft.com/en-us/library/cc645025(VS.95).aspx
			//http://www.google.si/search?hl=sl&q=silverlight+get+element+bounds&btnG=Iskanje&meta=

			bounds = new Rect();
			bool completed = false;
			Point position = new Point();
			if (element != null && GetAbsolutePosition(element, ref position))
			{
				Size size = Size.Empty;
				if (element is Shape)
				{
					//TODO: Get size from path
				}
				else if (!element.DesiredSize.IsEmpty)
				{
					size = element.DesiredSize;
				}
				else if (double.IsNaN(element.Width) || double.IsNaN(element.Height))
				{
					size = new Size(element.ActualWidth, element.ActualHeight);
				}
				else
				{
					size = new Size(element.Width, element.Height);
				}

				//TODO: If element has TranslateTransform, ScaleTransform, SkewTransform...

				bounds = new Rect(position, element.DesiredSize);
				completed = !size.IsEmpty;
			}
			return completed;
		}

		//Under construction
		public static Rect GetAbsoluteBounds(FrameworkElement element)
		{
			Rect bounds = Rect.Empty;
			bool completed = false;
			Point position = new Point();
			if (element != null && GetAbsolutePosition(element, ref position))
			{
				Size size = Size.Empty;
				if (double.IsNaN(element.Width) || double.IsNaN(element.Height))
				{
					size = new Size(element.ActualWidth, element.ActualHeight);
				}
				else if (!(double.IsNaN(element.Width) || double.IsNaN(element.Height)))
				{
					size = new Size(element.Width, element.Height);
				}
				else if (element is Shape)
				{
					//TODO: Get size from path
				}
				else if (!element.DesiredSize.IsEmpty)
				{
					size = element.DesiredSize;
				}
				else if (!element.RenderSize.IsEmpty)
				{
					size = element.RenderSize;
				}

				//TODO: If element has TranslateTransform, ScaleTransform, SkewTransform...

				bounds = new Rect(position, size);
				completed = !size.IsEmpty;
			}
			return bounds;
		}

		//Ref: http://www.andybeaulieu.com/Default.aspx?tabid=67&EntryID=95
		public static bool CheckCollision(FrameworkElement element1, FrameworkElement element2)
		//public static bool CheckCollision(Rect rect1, FrameworkElement element1, Rect rect2, FrameworkElement element2)
		{
			element1.Width = 200;
			element1.Height = 200;
			element2.Width = 200;
			element2.Height = 200;

			// first see if sprite rectangles collide
			Rect rect1 = GetAbsoluteBounds(element1);
			Rect rect2 = GetAbsoluteBounds(element2);

			//Commented - ignore shortcut
			//rect1.Intersect(rect2);
			//if (rect1 == Rect.Empty)
			//{
			//    // no collision - GET OUT!
			//    return false;
			//}
			//else
			{
				bool bCollision = false;
				Point ptCheck = new Point();

				// now we do a more accurate pixel hit test
				for (int x = Convert.ToInt32(rect1.X); x < Convert.ToInt32(rect1.X + rect1.Width); x++)
				{
					for (int y = Convert.ToInt32(rect1.Y); y < Convert.ToInt32(rect1.Y + rect1.Height); y++)
					{
						ptCheck.X = x;
						ptCheck.Y = y;

						List<UIElement> hits = VisualTreeHelper.FindElementsInHostCoordinates(ptCheck, element1) as List<UIElement>;
						if (hits.Contains(element1))
						{
							// we have a hit on the first control elem, now see if the second elem has a similar hit
							List<UIElement> hits2 = VisualTreeHelper.FindElementsInHostCoordinates(ptCheck, element2) as List<UIElement>;
							if (hits2.Contains(element2))
							{
								bCollision = true;
								break;
							}
						}
					}
					if (bCollision) break;
				}
				return bCollision;
			}
		}

		//Ref: http://www.andybeaulieu.com/Default.aspx?tabid=67&EntryID=95
		public static Rect UserControlBounds(FrameworkElement control)
		{
			Point ptTopLeft = new Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)), Convert.ToDouble(control.GetValue(Canvas.TopProperty)));
			Point ptBottomRight = new Point(Convert.ToDouble(control.GetValue(Canvas.LeftProperty)) + control.Width, Convert.ToDouble(control.GetValue(Canvas.TopProperty)) + control.Height);
			return new Rect(ptTopLeft, ptBottomRight);
		}
	}
}
